package com.bjtu.ptsite.tracker.client;

import bt.Bt;
import bt.data.Storage;
import bt.data.file.FileSystemStorage;
import bt.dht.DHTConfig;
import bt.dht.DHTModule;
import bt.runtime.BtClient;
import bt.runtime.Config;
import bt.torrent.maker.TorrentBuilder;
import com.bjtu.ptsite.mapper.PeerMapper;
import com.bjtu.ptsite.model.PeerMessage;
import com.bjtu.ptsite.model.User;
import com.bjtu.ptsite.tracker.entity.TrackerMessage;
import com.bjtu.ptsite.tracker.entity.TrackerRequest;
import com.bjtu.ptsite.utils.RequestUtil;
import com.bjtu.ptsite.utils.TorrentUtils;
import com.bjtu.ptsite.utils.UUIDUtils;
import com.turn.ttorrent.bcodec.BDecoder;
import com.turn.ttorrent.bcodec.BEValue;
import com.turn.ttorrent.bcodec.BEncoder;
import com.turn.ttorrent.client.Client;
import com.turn.ttorrent.client.SharedTorrent;
import com.turn.ttorrent.common.Torrent;
import com.turn.ttorrent.tracker.TrackedPeer;
import com.turn.ttorrent.tracker.TrackedTorrent;
import com.turn.ttorrent.tracker.Tracker;
import lombok.Getter;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.codec.binary.Hex;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Component;
import org.springframework.web.multipart.MultipartFile;

import java.io.*;
import java.net.*;
import java.nio.ByteBuffer;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.security.NoSuchAlgorithmException;
import java.time.LocalDate;
import java.time.format.DateTimeFormatter;
import java.util.*;
import java.util.concurrent.ConcurrentHashMap;

import static com.bjtu.ptsite.utils.StreamConvertToByte.inputStreamToByteArray;
import static com.bjtu.ptsite.utils.TorrentUtils.parseTorrent;

/**
 *
 * @author Zehao Wang, Junjie Zhang, Yutao Wei
 *
 */
@Slf4j
@Component
public class TrackerClient {

//    private final Config config;
//    private final DHTModule dhtModule;
//
//    @Autowired
//    public TrackerClient(Config config, DHTModule dhtModule) {
//        this.config = config;
//        this.dhtModule = dhtModule;
//    }
// enable multithreaded verification of torrent data


    @Value("${pt_service.net.newremote}")
    private String announceBaseUrl;


    @Autowired
    private PeerMapper peerMapper;

    private String downloadDir = "E:\\torrent\\download";
    private String seedDir = "E:\\torrent\\seed-folder";

    @Getter
    private Tracker tracker;

    Map<String, Client> seederMap = new ConcurrentHashMap<>();
    private static final String TORRENT_FILE_EXTENSION = ".torrent";

    public void startSeedingIfNotStarted(String torrentPath, File seedDir) {
        try {
            if (!seederMap.containsKey(torrentPath)) {
                SharedTorrent torrent = SharedTorrent.fromFile(new File(torrentPath), seedDir);
                InetAddress publicAddress = InetAddress.getByName("0.0.0.0");
                InetAddress localHost = InetAddress.getLocalHost();
                Client seeder = new Client(localHost, torrent);

                // 用单独线程启动，防止阻塞或结束后Seeder终止
                Thread seederThread = new Thread(seeder::share);
                seederThread.setDaemon(false);
                seederThread.start();

                seederMap.put(torrentPath, seeder);
                log.info("Seeder started for torrent: {}", torrentPath);
            }
        } catch (Exception e) {
            log.error("startSeedingIfNotStarted error: {}", e.getMessage(), e);
        }
    }

    /**
     * 启动并返回 Tracker 实例，端口默认为 6969
     */
    public Tracker startTracker() throws IOException {
        if (tracker != null) {
            // 避免重复启动
            return tracker;
        }
        InetSocketAddress trackerAddress = new InetSocketAddress("0.0.0.0", 6969);
        tracker = new Tracker(trackerAddress);
        tracker.start();
        return tracker;
    }

    /**
     * 注册一个 .torrent 文件到 Tracker 上
     */
    public void registerTorrentToTracker(File torrentFile) {
        try {
            if (tracker == null) {
                throw new IllegalStateException("Tracker 尚未启动");
            }
            TrackedTorrent tracked = TrackedTorrent.load(torrentFile);
            tracker.announce(tracked);
        } catch (Exception e) {
            log.error("注册种子失败:{}", e.getMessage(), e);
        }
    }

    /**
     * 启动 Seeder 客户端
     */
    public Client startSeeder(File torrentFile, File seedDirectory) throws UnknownHostException, IOException, NoSuchAlgorithmException {
        SharedTorrent sharedTorrent = SharedTorrent.fromFile(torrentFile, seedDirectory);
        // 替换为你的真实公网 IP
        InetAddress publicAddress = InetAddress.getByName("123.57.185.85");
        Client seeder = new Client(publicAddress, sharedTorrent);
        seeder.share();
        System.out.println("Seeder 正在分享种子文件...");
        return seeder;
    }


    /**
     * 启动 Downloader 客户端
     */
    public Client startDownloader(File torrentFile, File downloadDirectory) throws UnknownHostException, IOException, InterruptedException, NoSuchAlgorithmException {
        SharedTorrent sharedTorrent = SharedTorrent.fromFile(torrentFile, downloadDirectory);
        Client downloader = new Client(InetAddress.getLocalHost(), sharedTorrent);
        downloader.download();
        System.out.println("Downloader 开始下载...");
        downloader.waitForCompletion();
        System.out.println("下载完成！");
        return downloader;
    }

    /**
     * 保存 MultipartFile 为临时 .torrent 文件
     */
    public File saveUploadedTorrent(MultipartFile torrentFile) throws IOException {
        InputStream inputStream = torrentFile.getInputStream();
        File tempTorrentFile = File.createTempFile("upload-", ".torrent");
        try (OutputStream out = new FileOutputStream(tempTorrentFile)) {
            inputStream.transferTo(out);
        }
        return tempTorrentFile;
    }


    public void downLoadTorrent(TrackerRequest param, MultipartFile torrentFile) {
        try {
            InputStream inputStream = torrentFile.getInputStream();
            File tempTorrentFile = File.createTempFile("upload-", ".torrent"
            );
            try (OutputStream out = new FileOutputStream
                    (tempTorrentFile)) {
                inputStream.transferTo(out);
            }

            // 包含 abc.txt 的目录
            File seedFile = new File(seedDir);
            // 下载保存目录
            File downloadFile = new File(downloadDir);

            // 1. 启动 Tracker
            InetSocketAddress trackerAddress = new InetSocketAddress("0.0.0.0", 6969);
            Tracker tracker = new Tracker(trackerAddress);

            // 注册 .torrent
            TrackedTorrent trackedTorrent = TrackedTorrent.load(tempTorrentFile);
            tracker.announce(trackedTorrent);
            tracker.start();

            // 2. 启动 Seeder（拥有完整文件的客户端）
            SharedTorrent seedTorrent = SharedTorrent.fromFile(tempTorrentFile, seedFile);
            Client seeder = new Client(InetAddress.getLocalHost(), seedTorrent);
            seeder.share();
            System.out.println("Seeder 正在分享种子文件...");

            Thread.sleep(3000);

            // 3. 启动 Downloader（尝试下载）
            SharedTorrent downloadTorrent = SharedTorrent.fromFile(tempTorrentFile, downloadFile);
            Client downloader = new Client(InetAddress.getLocalHost(), downloadTorrent);
            downloader.download();
            System.out.println("Downloader 开始下载...");

            // 阻塞直到下载完成
            downloader.waitForCompletion();
            System.out.println("下载完成！");
        } catch (Exception e) {
            log.error("下载种子文件失败：{}", e.getMessage(), e);
        }

    }

    /**
     * 打印当前 Tracker 已注册的所有种子 infoHash 和名称
     */
    public void dumpRegisteredTorrents() {
        if (tracker == null) {
            log.warn("Tracker 尚未启动");
            return;
        }
        Collection<TrackedTorrent> torrents = tracker.getTrackedTorrents();
        log.info("=== 当前已注册种子共 {} 个 ===", torrents.size());
        for (TrackedTorrent t : torrents) {
            // 16 进制显示 infoHash
            String hex = t.getHexInfoHash();
            // 播放器里显示的名称（Torrent 内部的 name 字段）
            String name = t.getName();
            log.info("  • {}  (infoHash={})", name, hex);
        }
    }

    public String createTorrent(MultipartFile file, String messageId, String passKey) {
        try {
            String originalFilename = file.getOriginalFilename();
            if (originalFilename == null) {
                throw new IllegalArgumentException("文件名为空");
            }
            if (originalFilename.endsWith(TORRENT_FILE_EXTENSION)) {
                // 处理已有 .torrent 文件
                byte[] bytes = inputStreamToByteArray(file.getInputStream());
                Torrent torrent = new Torrent(bytes, false);

                byte[] finalBytes = containsMyAnnounce(torrent, announceBaseUrl)
                        ? bytes
                        : replaceAnnounceUrl(bytes, announceBaseUrl + passKey);

                Path tempTorrentPath = Files.createTempFile("torrent-" + messageId + "-", ".torrent");
                Files.write(tempTorrentPath, finalBytes);
                return tempTorrentPath.toAbsolutePath().toString();
            } else {
                URI uri = new URI(announceBaseUrl + passKey);
                Path tempDir = Files.createTempDirectory("torrent-");
                Path targetPath = tempDir.resolve(originalFilename);
                file.transferTo(targetPath.toFile());

                List<List<URI>> list=new ArrayList<>();
                list.add(List.of(uri));
                Torrent torrent = Torrent.create(targetPath.toFile(),2*1024*1024, list, "ttorrent");
                Path tempTorrentPath = Files.createTempFile("torrent-" + messageId + "-", ".torrent");
                Files.write(tempTorrentPath, torrent.getEncoded());
                return tempTorrentPath.toAbsolutePath().toString();
            }

        } catch (Exception e) {
            log.error("生成种子文件失败：{}", e.getMessage(), e);
            return null;
        }
    }


    public byte[] replaceAnnounceUrl(byte[] torrentBytes, String newAnnounce) throws IOException {
        Map<String, BEValue> decoded = BDecoder.bdecode(new ByteArrayInputStream(torrentBytes)).getMap();

        // 设置单一 announce 字段
        decoded.put("announce", new BEValue(newAnnounce));

//        // 构造新的 announce-list（可选）

        // 重新编码为 byte[]
        ByteArrayOutputStream out = new ByteArrayOutputStream();
        BEncoder.bencode(decoded, out);
        return out.toByteArray();
    }


    public boolean containsMyAnnounce(Torrent torrent, String myAnnounceUrl) {
        List<List<URI>> announceList = torrent.getAnnounceList();
        for (List<URI> tier : announceList) {
            for (URI uri : tier) {
                if (uri.toString().startsWith(myAnnounceUrl)) {
                    return true;
                }
            }
        }
        return false;
    }

    public String downloadTorrent(TrackerMessage trackerMessage, String passKey) {
        try {
            // HTTPS Torrent URL
            String torrentUrl = trackerMessage.getTorrentPath();
            URL url = new URL(torrentUrl);
            HttpURLConnection connection = (HttpURLConnection) url.openConnection();
            connection.setRequestMethod("GET");

            try (InputStream inputStream = connection.getInputStream()) {
                byte[] originalBytes = inputStream.readAllBytes();
                String announceUrl = announceBaseUrl + passKey;
                byte[] finalBytes = replaceAnnounceUrl(originalBytes, announceUrl);

                // 使用系统临时目录创建 .torrent 临时文件
                Path tempTorrentPath = Files.createTempFile("torrent-", ".torrent");

                // 保存处理后的 bytes 到临时文件
                Files.write(tempTorrentPath, finalBytes);

                return tempTorrentPath.toAbsolutePath().toString();
            }
        } catch (Exception e) {
            log.error("下载种子文件失败：{}", e.getMessage(), e);
            return null;
        }
    }


    public void don() throws IOException, NoSuchAlgorithmException {
        File torrentFile = new File("E:\\torrent\\torrentRes\\语音.xlsx.torrent");
        // 下载内容目录（种子对应的文件目录）
        File downloadDir = new File("E:\\torrent\\seed-folder");
        // 一个新空目录
        File downloadTarget = new File("E:\\torrent\\downloaded_output");


//        // 注册 .torrent
        // 载入种子
        SharedTorrent torrent = SharedTorrent.fromFile(torrentFile, downloadDir);

        // 创建客户端，绑定本机IP
        Client client = new Client(InetAddress.getLocalHost(), torrent);

        client.share();

        System.out.println("Seeder started, infoHash: " + torrent.getHexInfoHash());


        // 3. 启动 Downloader（尝试下载）
        SharedTorrent downloadTorrent = SharedTorrent.fromFile(torrentFile, downloadTarget);
        Client downloader = new Client(InetAddress.getLocalHost(), downloadTorrent);
        downloader.download();
        System.out.println("Downloader 开始下载...");

        // 阻塞直到下载完成
        downloader.waitForCompletion();

        System.out.println("Seeder completed.");
    }

    public List<Map<String, Object>> listPeers(String infoHash) {
        try {

            TrackedTorrent torrent = tracker.getTrackedTorrents().stream()
                    .filter(t -> infoHash.equalsIgnoreCase(t.getHexInfoHash()))
                    .findFirst()
                    .orElse(
                            null
                    );

            if (torrent == null) {
                return null;
            }

            List<Map<String, Object>> peerInfoList = new ArrayList<>();
            Map<String, TrackedPeer> peers = torrent.getPeers();
            for (TrackedPeer peer : peers.values()) {
                Map<String, Object> peerInfo = new HashMap<>(4);
                peerInfo.put("ip", peer.getIp());
                peerInfo.put("port", peer.getPort());
                peerInfo.put("isCompleted", peer.isCompleted());
                peerInfoList.add(peerInfo);
            }

            return peerInfoList;
        } catch (Exception e) {
            log.error("获取种子文件失败：{}", e.getMessage(), e);
            return null;
        }
    }

    public String uploadTorrent(MultipartFile file, String messageId, String passKey) {
        try {
            String originalFilename = file.getOriginalFilename();
            if (originalFilename == null) {
                throw new IllegalArgumentException("文件名为空");
            }

            if (originalFilename.endsWith(TORRENT_FILE_EXTENSION)) {
                // 处理已有 .torrent 文件
                byte[] bytes = inputStreamToByteArray(file.getInputStream());
                Torrent torrent = new Torrent(bytes, false);

                byte[] finalBytes = containsMyAnnounce(torrent, announceBaseUrl)
                        ? bytes
                        : replaceAnnounceUrl(bytes, announceBaseUrl + passKey);

                // 创建临时 .torrent 文件
                Path tempTorrentPath = Files.createTempFile("torrent-" + messageId + "-", ".torrent");
                Files.write(tempTorrentPath, finalBytes);

                return tempTorrentPath.toAbsolutePath().toString();

            } else {
                 //保存源文件为临时 seed 文件
                long l = System.currentTimeMillis();
                Path tempDir = Files.createTempDirectory("source-" + messageId + "-");
                Path targetPath = tempDir.resolve(originalFilename);
                file.transferTo(targetPath.toFile());
                long l2 = System.currentTimeMillis();
                log.info("保存源文件耗时：{}ms", l2 - l);
                URI uri = new URI(announceBaseUrl + passKey);

                long l1 = System.currentTimeMillis();
                List<List<URI>> list=new ArrayList<>();
                list.add(List.of(uri));
                Torrent torrent = Torrent.create(targetPath.toFile(),2*1024*1024, list, "ttorrent");
                long l3 = System.currentTimeMillis();
                // 构造 Tracker announce 地址

                log.info("创建种子文件耗时：{}ms", l3 - l1);
                // 创建 Torrent 对象

                // 创建临时 .torrent 文件
                Path tempTorrentPath = Files.createTempFile("torrent-" + messageId + "-", ".torrent");
                Files.write(tempTorrentPath, torrent.getEncoded());

                long l4 = System.currentTimeMillis();
                log.info("保存种子文件耗时：{}ms", l4 - l3);
                return tempTorrentPath.toAbsolutePath().toString();

            }

        } catch (Exception e) {
            log.error("生成种子文件失败：{}", e.getMessage(), e);
            return null;
        }
    }

    /**
     * 检测连接性
     * @param infoHash
     * @return
     */
    public boolean isConnect(String infoHash) {
        List<PeerMessage> peerMessages = peerMapper.selectByInfoHash(infoHash);
        for(PeerMessage peerMessage:peerMessages){

            Integer port = peerMessage.getPort();
            String ip = peerMessage.getIp();
            if(testConnect(ip,port,2000)) {
                //有一个能连接说明这个种子可用，是活种
                return true;
            }
        }

        return false;

    }


    private boolean testConnect(String ip, Integer port,int timeoutMillis) {
        try (Socket socket = new Socket()) {
            SocketAddress address = new InetSocketAddress(ip, port);
            // 设置连接超时
            socket.connect(address, timeoutMillis);
            return true;
        } catch (Exception e) {
            return false;
        }
    }
}


